﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.ComponentModel;
using System.Linq;

namespace Components.Monetario
{
    [DefaultBindingProperty("Valor")]
    public class Monetario: TextBox
    {
        Decimal _valor;

        public Decimal Valor
        {
            get { return _valor; }
            set 
            {
                _valor = value;
                this.Text = Monetario.ValorExtenso(value, TipoMoeda);
            }
        }

        [DefaultValue(Moeda.real)]
        public Moeda TipoMoeda
        {
            get;
            set;
        }

        public Monetario()
        {
            TipoMoeda = Moeda.real;
        }

        #region Função para conversão valor real por extenso

        static readonly Dictionary<int, string> radicaisPorExtenso = new Dictionary<int, string>()
        {
            {0, "Zero"},
            {1, "Um"},
            {2, "Dois"},
            {3, "Três"},
            {4, "Quatro"},
            {5, "Cinco"},
            {6, "Seis"},
            {7, "Sete"},
            {8, "Oito"},
            {9, "Nove"},
            {10, "Dez"},
            {11, "Onze"},
            {12, "Doze"},
            {13, "Treze"},
            {14, "Quatorze"},
            {15, "Quinze"},
            {16, "Dezesseis"},
            {17, "Dezesete"},
            {18, "Dezoito"},
            {19, "Dezenove"},
            {20, "Vinte"},
            {30, "Trinta"},
            {40, "Quarenta"},
            {50, "Cinquenta"},
            {60, "Sessenta"},
            {70, "Setenta"},
            {80, "Oitenta"},
            {90, "Noventa"},
            {100, "Cem"},
            {200, "Duzentos"},
            {300, "Trezentos"},
            {400, "Quatrocentos"},
            {500, "Quinhentos"},
            {600, "Seiscentos"},
            {700, "Setecentos"},
            {800, "Oitocentos"},
            {900, "Novecentos"}
        };

        static readonly Dictionary<int, string> categoriaDaCentenaPlural = new Dictionary<int, string>()
        {
             {0, ""},
             {1, " Mil"},
             {2, " Milhões"},
             {3, " Bilhões"},
             {4, " Trilhões"},
             {5, " Quadrilhões"},
             {6, " Quintilhões"},
             {7, " Sextilhões"},
             {8, " Septilhões"},
             {9, " Octlhões"}
        };

        static readonly Dictionary<int, string> categoriaDaCentenaSingular = new Dictionary<int, string>()
        {
             {0, ""},
             {1, " Mil"},
             {2, " Milhão"},
             {3, " Bilhão"},
             {4, " Trilhão"},
             {5, " Quadrilhão"},
             {6, " Quintilhão"},
             {7, " Sextilhão"},
             {8, " Septilhão"},
             {9, " Octlhão"}
        };

        public enum Moeda { real, dolar };

        /// <summary>
        /// Função para escrever valores em moeda por extenso
        /// Não aceita números negativos
        /// </summary>
        /// <param name="valor">Valor para ser convertido em extenso</param>
        /// <returns>String do valor por Extenso</returns>
        public static string ValorExtenso(decimal valor)
        {
            return ValorExtenso(valor, Moeda.real);
        }

        /// <summary>
        /// Função para escrever valores em moeda por extenso
        /// Não aceita números negativos
        /// </summary>
        /// <param name="valor">Valor para ser convertido em extenso</param>
        /// <returns>String do valor por Extenso</returns>
        public static string ValorExtenso(decimal valor, Moeda moeda)
        {
            string moedaPlural;
            string moedaSingular;
            string simbolo;
            if (moeda == Moeda.real)
            {
                simbolo = "R$";
                moedaPlural = " Reais";
                moedaSingular = " Real";
            }
            else
            {
                simbolo = "U$";
                moedaPlural = " Dolares";
                moedaSingular = " Dolar";
            }

            string parteInteiraPorExtenso = string.Empty;
            string parteFracionadaPorExtenso = string.Empty;
            List<KeyValuePair<int, string>> centenasDoValorPorExtenso = new List<KeyValuePair<int, string>>();

            if (valor > decimal.MaxValue)
                throw new ArgumentException("O valor supera o máximo permitido!");

            if (valor < decimal.Zero)
                throw new ArgumentException("O valor não pode ser menor que zero!");

            if (valor == decimal.Zero)
                return String.Format("{0} Zero{1}", simbolo, moedaPlural);


            valor = NormalizaValor(valor);

            decimal parteInteira = Math.Floor(valor);
            int parteFracionada = Convert.ToInt32(Math.Round(valor - Math.Floor(valor), 2) * 100);

            if (parteInteira > 0)
            {
                IEnumerable<int> numeroDesmembradoEmCentenas = DesmembraParteInteiraEmCentenas(parteInteira);
                numeroDesmembradoEmCentenas = numeroDesmembradoEmCentenas.Reverse();

                for (int nivelCentena = 0; nivelCentena < numeroDesmembradoEmCentenas.Count(); nivelCentena++)
                {
                    int centena = numeroDesmembradoEmCentenas.ElementAt(nivelCentena);

                    string porExtenso = RecuperaCentenaPorExtenso(centena) + RecuperaCategoriaCentena(nivelCentena, centena);
                    KeyValuePair<int, string> valorEPorExtenso = new KeyValuePair<int, string>(centena, porExtenso);

                    if (valorEPorExtenso.Key > 0)
                        centenasDoValorPorExtenso.Add(valorEPorExtenso);
                }

                centenasDoValorPorExtenso.Reverse();
                foreach (KeyValuePair<int, string> valorEPorExtenso in centenasDoValorPorExtenso)
                {
                    if (radicaisPorExtenso.Keys.Contains(valorEPorExtenso.Key) || valorEPorExtenso.Key < 100)
                        parteInteiraPorExtenso += string.Format(" e {0}", valorEPorExtenso.Value);
                    else
                        parteInteiraPorExtenso += string.Format(", {0}", valorEPorExtenso.Value);
                }

                parteInteiraPorExtenso = parteInteiraPorExtenso.TrimStart(' ', 'e', ',');

                if (numeroDesmembradoEmCentenas.Count() > 2 && centenasDoValorPorExtenso.Count == 1)
                    parteInteiraPorExtenso += string.Format(" de{0}", moedaPlural);
                else
                    parteInteiraPorExtenso += (parteInteira > 1) ? moedaPlural : moedaSingular;
            }


            if (parteFracionada > 0)
            {
                if (parteInteira > 0)
                    parteFracionadaPorExtenso = string.Format(" e {0}", RecuperaCentenaPorExtenso(parteFracionada));
                else
                    parteFracionadaPorExtenso = RecuperaCentenaPorExtenso(parteFracionada);

                parteFracionadaPorExtenso += parteFracionada > 1 ? " Centavos" : " Centavo";
            }

            return string.Format("{0} {1}{2}", simbolo, parteInteiraPorExtenso, parteFracionadaPorExtenso);
        }


        private static string RecuperaCategoriaCentena(int nivel, int centena)
        {
            return (centena > 1) ? categoriaDaCentenaPlural[nivel] : categoriaDaCentenaSingular[nivel];
        }

        private static decimal NormalizaValor(decimal valor)
        {
            string valorString = valor.ToString().Trim();
            int index = valorString.IndexOf(',');
            valor = decimal.Parse(index < 0 ? string.Format("{0},00", valorString) : string.Format("{0},{1}", valorString.Substring(0, index), valorString.Substring(index + 1).PadRight(2, '0')));
            return valor;
        }

        private static string RecuperaCentenaPorExtenso(int centena)
        {
            if (centena == 0)
                return string.Empty;

            List<string> algarismoPorExtenso = new List<string>();

            while (centena > 0)
            {
                if (radicaisPorExtenso.Keys.Contains(centena))
                {
                    algarismoPorExtenso.Add(radicaisPorExtenso[centena]);
                    break;
                }
                else
                {
                    int radicalBase = (from radical in radicaisPorExtenso.Keys
                                       where radical < centena
                                       select radical).Max();

                    if (EstaEntreCemEDuzentos(centena))
                        algarismoPorExtenso.Add("Cento");
                    else
                        algarismoPorExtenso.Add(radicaisPorExtenso[radicalBase]);

                    centena -= radicalBase;
                }
            }

            return (algarismoPorExtenso.Count > 1) ? algarismoPorExtenso.Aggregate((current, next) => string.Format("{0} e {1}", current, next)) : algarismoPorExtenso.FirstOrDefault();
        }

        private static bool EstaEntreCemEDuzentos(int valor)
        {
            return valor > 100 && valor < 200;
        }

        private static decimal CalculaDivisorInicial(decimal parteInteira)
        {
            decimal divisor = 1000;

            int i = 1;
            int numeroDeCentenas = (parteInteira.ToString().Length - 1) / 3;

            while (i < numeroDeCentenas)
            {
                divisor *= 1000;
                i++;
            }

            return divisor;
        }

        private static IEnumerable<int> DesmembraParteInteiraEmCentenas(decimal parteInteira)
        {
            int quociente;
            decimal resto;
            decimal divisor = CalculaDivisorInicial(parteInteira);

            if (parteInteira.ToString().Length > 3)
            {
                resto = parteInteira;

                while (divisor > 100)
                {
                    quociente = Convert.ToInt32(Math.Floor(resto / divisor));
                    resto = resto % divisor;

                    divisor /= 1000;

                    yield return quociente;
                }

                yield return Convert.ToInt32(resto);
            }
            else
            {
                yield return Convert.ToInt32(parteInteira);
            }
        }

        #endregion
    }
}
